﻿using System;
using System.Net;
using System.Net.Browser;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;

namespace DnnDash.SimplePhoneApp.Common
{
    internal enum CallType
    {
        GET, POST, PUT, DELETE, HEAD
    }
    internal class ServiceRequest<T>
    {
        private AppSettings _settings;
        protected string _callName;
        protected string _responseType;
        protected CallType _callType;
        protected const string ModulePath = "DnnDash_SimpleDashboard";
        protected const string ModuleControllerName = "Dash";
        protected bool _anonymous;
        protected bool _secure;
        
        #region properties
        internal HttpStatusCode StatusCode { get; set; }
        internal string ErrorMessage { get; set; }
        internal string RequestBody { get; set; }
        internal string CallName { get { return _callName; } }
        internal CallType CallType { get { return _callType; } }
        #endregion

        #region events
        internal delegate void CallReturnedHandler(object sender, ServiceResponseEventArgs<T> e);
        internal event CallReturnedHandler CallReturned;
        #endregion
        /// <summary>
        /// Constructor for new Service Request 
        /// </summary>
        /// <param name="settings">Current application settings</param>
        /// <param name="callType">Get/Put/Post etc</param>
        /// <param name="responseType">Describes the type of response.  Can be Xml/Json/String/Etc</param>
        /// <param name="callName">Name of DotNetNuke API Call (from API Controller method)</param>
        /// <param name="anonymous">if true, uses no authentication.  If false, usese authentication from settings</param>
        /// <param name="secure">if true, uses a Https request.  If false, uses a http request.</param>
        internal ServiceRequest(AppSettings settings, CallType callType, string responseType, string callName, bool anonymous, bool secure)
        {
            StatusCode = HttpStatusCode.Unused;
            _settings = settings;
            _callName = callName;
            _callType = callType;
            _responseType = responseType;
            _anonymous = anonymous;
            _secure = secure;
            
        }
        /// <summary>
        /// Call the Configured DotNetNuke Service
        /// </summary>
        internal void CallService()
        {
            if (CallReturned == null)
                throw new NullReferenceException("No CallReturned Event Handler defined, Response will never be raised");
            else
            {
                //get the service Uri for the Dnn API Service
                Uri serviceUri = Helper.GetUri(_settings.DomainAlias, ModulePath, ModuleControllerName, _callName, _secure);
                //create request object for the service Uri
                HttpWebRequest httpReq = (HttpWebRequest)HttpWebRequest.Create(serviceUri);
                //if not an anonoymous call, add the network credentials for authorisation
                if (_anonymous == false)
                    httpReq.Credentials = new NetworkCredential(_settings.Username, _settings.Password);
                //set the get/post etc method for the request
                httpReq.Method = MethodFromCallType(_callType);
                //set the accept header for the correct result as per the webAPI content negotiation
                if (_responseType == "json")
                    httpReq.Accept = "application/json";
                if (_responseType == "xml")
                    httpReq.Accept = "application/xml";

                //if the method requires a request body
                if (_callType == Common.CallType.POST || _callType == Common.CallType.PUT)
                {
                    switch (_responseType.ToLower())
                    {
                        case "json":
                            httpReq.ContentType = "application/json";
                            break;
                        case "xml":
                            httpReq.ContentType = "application/xml";
                            break;
                        default:
                            httpReq.ContentType = "application/x-www-form-urlencoded";
                            break;
                    }
                }

                //start the call
                httpReq.BeginGetResponse(HttpWebRequestCallBack, httpReq);
            }
        }
        /// <summary>
        /// Callback method for writing to the Request body
        /// </summary>
        /// <param name="asynchronousResult"></param>
        private void HttpWebRequestStreamCallBack(IAsyncResult asynchronousResult)
        {
            HttpWebRequest httpReq = (HttpWebRequest)asynchronousResult.AsyncState;
            if (httpReq != null)
            {
                Stream postStream = httpReq.EndGetRequestStream(asynchronousResult);
                byte[] byteArray = Encoding.UTF8.GetBytes(this.RequestBody);
                //add post data to web request by writing to the stream
                postStream.Write(byteArray, 0, byteArray.Length);
                postStream.Close();
            }
        }
        /// <summary>
        /// Callback when the Http Web Request is returned
        /// </summary>
        /// <param name="result">The Async Object used to make the request</param>
        /// <remarks>Results from the callback are raised in the CallReturned Event</remarks>
        private void HttpWebRequestCallBack(IAsyncResult result)
        {
            try
            {
                HttpWebRequest request = (HttpWebRequest)result.AsyncState;
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
                Stream stream = response.GetResponseStream();
                StreamReader reader = new StreamReader(stream);
                

                ServiceResponseEventArgs<T> responseArgs = new ServiceResponseEventArgs<T>();
                responseArgs.StatusCode = response.StatusCode;
                responseArgs.ErrorMessage = null;
                responseArgs.CallName = CallName;
                responseArgs.ReturnValue = GetReturnValue(reader);
                this.StatusCode = response.StatusCode;
                this.ErrorMessage = null;
                if (CallReturned != null)
                    CallReturned(this, responseArgs);
                
            }
            catch (WebException webEx)
            {
                HttpWebResponse response = (HttpWebResponse)webEx.Response;
                this.StatusCode = (HttpStatusCode)response.StatusCode;
                this.ErrorMessage = webEx.Message;

                ServiceResponseEventArgs<T> responseArgs = new ServiceResponseEventArgs<T>();
                responseArgs.StatusCode = this.StatusCode;
                responseArgs.ErrorMessage = this.ErrorMessage;
                responseArgs.Exception = webEx;
                responseArgs.CallName = _callName;
                responseArgs.ReturnValue = default(T);
                if (CallReturned != null)
                    CallReturned(this, responseArgs);

            }
            catch (Exception ex)
            {
                this.StatusCode = HttpStatusCode.SeeOther;
                this.ErrorMessage = ex.Message;

                ServiceResponseEventArgs<T> responseArgs = new ServiceResponseEventArgs<T>();
                responseArgs.StatusCode = this.StatusCode;
                responseArgs.ErrorMessage = this.ErrorMessage;
                responseArgs.Exception = ex;
                responseArgs.CallName = _callName;
                responseArgs.ReturnValue = default(T);
                if (CallReturned != null)
                    CallReturned(this, responseArgs);

            }
        }
        /// <summary>
        /// The GetReturnValue call returns the results from the Http Web Request, converted to the
        /// type T as defined in the constructor of the object.
        /// </summary>
        /// <param name="reader">Stream of the response</param>
        /// <returns>Type T response, converted from the reader Stream</returns>
        private T GetReturnValue(StreamReader reader)
        {
            if (typeof(T) == typeof(string))
            {
                object stringResponse = reader.ReadToEnd();
                return (T)stringResponse;
            }
            else
            {
                if (_responseType.ToLower() == "json")
                {
                    //deserialise into json object of type T
                    object jsonObj = JsonSerializer.Deserialize<T>(reader.ReadToEnd());
                    return (T)jsonObj;
                }
                else
                {
                    if (_responseType.ToLower() == "xml") // && typeof(T) == typeof(XDocument))
                    {
                        //cast to xDocument from xml
                        //StringReader stringReader = new StringReader(xml);
                        XmlReader xmlReader = XmlReader.Create(reader);
                        object doc = XDocument.Load(xmlReader);
                        return (T)doc;
                    }
                    //else
                    //{
                    //    string xml = reader.ReadToEnd();
                    //    XmlReader xR = XmlReader.Create(reader);
                    //    var xDoc = XDocument.Load(xR);
                    //    object oxml = xDoc.ToString();
                    //    return (T)oxml;
                    //}
                    else
                    {
                    //    //try our luck with a generic conversion
                        object objResponse = reader.ReadToEnd();
                        return (T)objResponse;
                    }
                }
            }
        }
        private static string MethodFromCallType(Common.CallType callType)
        {
            switch (callType)
            {
                case CallType.DELETE:
                    return "DELETE";
                case CallType.GET:
                    return "GET";
                case CallType.HEAD:
                    return "HEAD";
                case CallType.POST:
                    return "POST";
                case CallType.PUT:
                    return "PUT";
                default:
                    return "UNKNOWN";
            }
        }
    }
    internal class ServiceResponseEventArgs<T> : EventArgs 
    {
        T _returnValue;
        internal ServiceResponseEventArgs() : base ()
        {

        }
        internal string CallName {get;set;}
        internal string ErrorMessage {get; set;}
        internal HttpStatusCode StatusCode {get;set;}
        internal Exception Exception { get; set; }
        internal T ReturnValue
        {
            get
            {
                return _returnValue;
            }
            set 
            {
                _returnValue = value;
            }
        }
    }
}
